home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume22 / nn6.4 / part15 < prev    next >
Encoding:
Internet Message Format  |  1990-06-07  |  54.2 KB

  1. Subject:  REPOST v22i052:  NN Newsreader, release 6.4, Part15/21
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4. X-Checksum-Snefru: ff1ae712 ab8ee557 42c00e18 11ad7385
  5.  
  6. Submitted-by: "Kim F. Storm" <storm@texas.dk>
  7. Posting-number: Volume 22, Issue 50
  8. Archive-name: nn6.4/part15
  9.  
  10. #! /bin/sh
  11. # This is a shell archive.  Remove anything before this line, then feed it
  12. # into a shell via "sh file" or similar.  To overwrite existing files,
  13. # type "sh file -c".
  14. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  15. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  16. # Contents:  data.h doc/PROBLEMS expire.c save.c
  17. # Wrapped by rsalz@litchi.bbn.com on Fri May 11 12:42:44 1990
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. echo If this archive is complete, you will see the following message:
  20. echo '          "shar: End of archive."'
  21. if test -f 'data.h' -a "${1}" != "-c" ; then 
  22.   echo shar: Will not clobber existing file \"'data.h'\"
  23. else
  24.   echo shar: Extracting \"'data.h'\" \(6540 characters\)
  25.   sed "s/^X//" >'data.h' <<'END_OF_FILE'
  26. X/*
  27. X *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  28. X *
  29. X *    Internal representation of the master, group, and article
  30. X *    information read from the database.
  31. X *
  32. X *    For each article read from the database, an article_header
  33. X *    structure is initialized.
  34. X */
  35. X
  36. X/*
  37. X *    The magic number allows us to change the format of the database
  38. X *    and tell the users about it
  39. X */
  40. X
  41. X#define NNDB_MAGIC    0x4e4e2302    /* NN#2 */
  42. X
  43. X/*
  44. X *    global master data
  45. X */
  46. X
  47. X#define    DB_LOCK_MESSAGE        80
  48. X
  49. Xtypedef struct {
  50. X    int32        db_magic;
  51. X    char        db_lock[DB_LOCK_MESSAGE];
  52. X    time_t        db_created; /* when database was last built */
  53. X    time_t        last_scan; /* age of active file at last scan */
  54. X    off_t        last_size; /* size of active file at last scan */
  55. X    group_number    number_of_groups;
  56. X    int            free_groups;
  57. X} master_header;
  58. X
  59. X/*
  60. X *    group information
  61. X */
  62. X
  63. Xtypedef struct group_header {
  64. X
  65. X    /* this part of the header is read from the MASTER file */
  66. X
  67. X    flag_type        master_flag;    /* master's flags */
  68. X
  69. X#    define M_VALID        FLAG(1)    /* group present in active file */
  70. X#    define M_CONTROL    FLAG(2)    /* group is control group */
  71. X#    define M_NO_DIRECTORY    FLAG(3)    /* group directory not found */
  72. X#    define M_ALWAYS_DIGEST    FLAG(4)    /* D in GROUPS */
  73. X#    define M_NEVER_DIGEST    FLAG(5)    /* N in GROUPS */
  74. X#    define M_EXPIRE        FLAG(6)    /* expire in progress or pending */
  75. X#    define M_BLOCKED    FLAG(7)    /* don't trust this entry */
  76. X#    define M_MUST_CLEAN    FLAG(8) /* group should be cleaned */
  77. X#    define M_MODERATED    FLAG(9)    /* group is moderated */
  78. X#    define M_ALIASED    FLAG(10) /* =xxx in active file */
  79. X#    define M_NOPOST        FLAG(11) /* 'n' in active file */
  80. X#    define M_AUTO_RECOLLECT    FLAG(12) /* R in GROUPS */
  81. X#    define M_AUTO_ARCHIVE    FLAG(13) /* >file in GROUPS */
  82. X#    define M_INCLUDE_OLD    FLAG(14) /* O in GROUPS */
  83. X#    define M_IGNORE_G    FLAG(15) /* ignore this group (GROUPS X) */
  84. X#    define M_IGNORE_A    FLAG(16) /* ignore this group (ACTIVE x) */
  85. X#    define M_IGNORE_GROUP    (M_IGNORE_G | M_IGNORE_A)
  86. X
  87. X    article_number    first_db_article; /* min article in db */
  88. X    article_number    last_db_article;  /* max article in db */
  89. X
  90. X    article_number    first_a_article; /* min article in active */
  91. X    article_number    last_a_article;  /* max article in active */
  92. X
  93. X    off_t        index_write_offset;
  94. X    off_t        data_write_offset;
  95. X
  96. X    time_t        creation_time; /* when group was created */
  97. X
  98. X    int            group_name_length;
  99. X
  100. X    /* this part is initialized during reading of the GROUPS file */
  101. X
  102. X    /* DO NOT CHANGE THE POSITION OF group_flag AS THE FIRST FIELD */
  103. X    /* AFTER THE PART WHICH IS SAVED IN THE MASTER FILE */
  104. X
  105. X    flag_type        group_flag;    /* client's flags */
  106. X
  107. X#    define G_UNSUBSCRIBED    FLAG(1)    /* ! in .newsrc */
  108. X#    define G_READ        FLAG(2)    /* group has been visited */
  109. X#    define G_FAKED        FLAG(3)    /* faked group - not in .newsrc */
  110. X#    define G_NEW        FLAG(5)    /* new group */
  111. X#    define G_FOLDER        FLAG(6)    /* "group" is a folder file */
  112. X#    define G_DIRECTORY    FLAG(7)    /* "group" is directory */
  113. X#    define G_SEQUENCE    FLAG(8)    /* in presentation sequence */
  114. X#    define G_DONE        FLAG(9)    /* sequence is done with group */
  115. X#    define G_MAILBOX    FLAG(10) /* user's mail box file */
  116. X#    define G_MERGE_HEAD    FLAG(11) /* merged group head */
  117. X#    define G_MERGE_SUB    FLAG(12) /* merged group sub element */
  118. X#    define G_MERGED     (G_MERGE_HEAD | G_MERGE_SUB)
  119. X#    define G_COUNTED    FLAG(13) /* counted in unread_count */
  120. X
  121. X    group_number    group_num;
  122. X    group_number    preseq_index;
  123. X
  124. X    char *        group_name;
  125. X
  126. X    char *        archive_file;
  127. X
  128. X    article_number    first_article;
  129. X    article_number    last_article;
  130. X
  131. X    article_number    current_first;    /* first article currently read in */
  132. X
  133. X    struct group_header *next_group;    /* group sequence */
  134. X    struct group_header    *prev_group;
  135. X
  136. X    struct group_header    *merge_with;     /* merged groups */
  137. X
  138. X    char        *kill_list;
  139. X    char        *save_file;     /* default save file from init */
  140. X    char        *enter_macro;
  141. X
  142. X    struct group_header    *newsrc_seq;    /* .newsrc sequence        */
  143. X    char        *newsrc_line;     /* .newsrc line for this group */
  144. X    char        *newsrc_orig;     /* original newsrc_line if changed */
  145. X    char        *select_line;    /* .nn/select line for this group */
  146. X    char        *select_orig;    /* original select_line if changed */
  147. X
  148. X    int32        unread_count;    /* number of unread articles */
  149. X} group_header;
  150. X
  151. X
  152. X/* size of the part of the group header placed on backing storage */
  153. X
  154. X
  155. X#define SAVED_GROUP_HEADER_SIZE(group) \
  156. X    (((char *)(&((group).group_flag))) - ((char *)(&(group))))
  157. X
  158. X/*
  159. X *    Internal article header information.
  160. X */
  161. X
  162. Xtypedef struct {
  163. X    union {
  164. X    article_number au_number;    /* article number in the group    */
  165. X    char *au_string;
  166. X    } au_union;
  167. X    group_header *a_group;    /* if merged article menu    */
  168. X
  169. X                /* indexes to header line text    */
  170. X    off_t    hpos;        /* first byte of header        */
  171. X    off_t    fpos;        /* first byte in article text    */
  172. X    off_t    lpos;        /* last pos of article        */
  173. X
  174. X    time_stamp     t_stamp;    /* encoded time_stamp        */
  175. X    time_stamp     root_t_stamp;    /* subject's time_stamp        */
  176. X
  177. X    char *    sender;        /* sender's name        */
  178. X    char *    subject;    /* subject (w/o Re:)        */
  179. X
  180. X    int16    replies;    /* no of Re:            */
  181. X    int16    lines;        /* no of lines        */
  182. X
  183. X    int8    subj_length;    /* length of subject        */
  184. X    int8    name_length;    /* length of sender        */
  185. X
  186. X    attr_type    attr;        /* attributes:             */
  187. X    attr_type    disp_attr;    /* currently displayed attr.    */
  188. X    /* warning: notice relation between A_SELECT and A_AUTO_SELECT */
  189. X
  190. X#    define A_READ        0x01    /* article has been read    */
  191. X#    define A_SEEN        0x02    /* article presented on menu    */
  192. X#    define A_LEAVE        0x03    /* marked for later activity    */
  193. X#    define A_LEAVE_NEXT    0x04    /* marked for next invokation    */
  194. X#    define A_CANCEL        0x05    /* folder entry cancelled    */
  195. X#    define A_KILL        0x06    /* eliminate article        */
  196. X#    define A_SELECT       0x08    /* article has been selected    */
  197. X#    define A_AUTO_SELECT    0x09    /* will match & A_SELECT    */
  198. X
  199. X#    define A_NOT_DISPLAYED    0x7f    /* not currently on menu    */
  200. X
  201. X    flag_type    flag;        /* attributes and flags:    */
  202. X
  203. X#    define A_SAME            FLAG(1)    /* same subject as prev. article */
  204. X#    define A_ALMOST_SAME    FLAG(2)    /* A_SAME (match-limit)        */
  205. X#    define A_NEXT_SAME    FLAG(4)    /* next is same subject        */
  206. X#    define A_DIGEST       FLAG(5)    /* digest sub article        */
  207. X#    define A_FULL_DIGEST    FLAG(6)    /* full digest            */
  208. X#    define A_FOLDER        FLAG(7)    /* article file = "folder_path"    */
  209. X#    define A_FAKED        FLAG(8)    /* only 'number' is valid    */
  210. X#    define A_ST_FILED    FLAG(10) /* articles is saved        */
  211. X#    define A_ST_REPLY    FLAG(11) /* sent reply to article    */
  212. X#    define A_ST_FOLLOW    FLAG(12) /* sent followup to article    */
  213. X
  214. X} article_header;
  215. X
  216. X
  217. X#define    a_number    au_union.au_number
  218. X#define a_string    au_union.au_string
  219. X
  220. END_OF_FILE
  221.   if test 6540 -ne `wc -c <'data.h'`; then
  222.     echo shar: \"'data.h'\" unpacked with wrong size!
  223.   fi
  224.   # end of 'data.h'
  225. fi
  226. if test -f 'doc/PROBLEMS' -a "${1}" != "-c" ; then 
  227.   echo shar: Will not clobber existing file \"'doc/PROBLEMS'\"
  228. else
  229.   echo shar: Extracting \"'doc/PROBLEMS'\" \(13534 characters\)
  230.   sed "s/^X//" >'doc/PROBLEMS' <<'END_OF_FILE'
  231. X                KNOWN PROBLEMS
  232. X                --------------
  233. X
  234. XHere is a collection of some of the problems people have had with
  235. Xinstallation and operation of nn in the past.  Some of these may no
  236. Xlonger be completely valid since release 6.4 has changed quite a
  237. Xnumber of things since release 6.3, but there may still be some hints
  238. Xto solving the problems you might encounter.
  239. X
  240. X
  241. X             RUNNING NN ON 80286
  242. X             -------------------
  243. X
  244. XThe system and machine file for a '286 running Microport UNIX V/AT are
  245. Xs-uport2-2.h and m-i80286.h, but to get it running you probably have
  246. Xto do the following things as well:
  247. X
  248. X- tgetstr.o in libcurses is broken in 2.4.
  249. X  Get an old version (e.g. 1.3) and replaced tgetstr.o.
  250. X
  251. X- Use the m286 malloc posted recently to comp.sources.misc
  252. X
  253. XThanks to Wietse Z. Venema and Miek Grenier for a lot of work on
  254. Xlocating and fixing 16/32 bit problems in nn 6.3 which caused it not
  255. Xto work on the '286.  (I hope I haven't introduced new problems in
  256. X6.4, but I cannot promise it).
  257. X
  258. X                NNTP PROBLEMS
  259. X                -------------
  260. X
  261. XOther problems with the current NNTP support are described in the NNTP
  262. Xfile.
  263. X
  264. X
  265. X               FILE PERMISSIONS
  266. X               ----------------
  267. X
  268. XYou should run as root when installing the package & db, because some
  269. Xdirectories might be created in places where ordinary users are not
  270. Xallowed to write, and secondly because it is not allowed to change the
  271. Xowner of a file (nnmaster) on some systems.
  272. X
  273. XSpecifically, nnmaster will report "nnmaster already running" if it
  274. Xdoesn't have write access to the MASTER_DIRECTORY.
  275. X
  276. XThis might be the cause of your problems.
  277. X
  278. X
  279. XIn general, the permissions and ownership of the various programs
  280. Xshould be set to allow the following access:
  281. X
  282. X- To let the nnmaster READ the news directory (no problem since
  283. X  /usr/spool/news normally has mode 755),
  284. X
  285. X- To let the nnmaster WRITE in the db-directory and files
  286. X
  287. X- To let ordinary users programs (i.e. nn) READ the db-directory.
  288. X
  289. XFor example,
  290. X              owner   group   mode
  291. X  db-directory & files:    news    news    755
  292. X  nnmaster:        news    news    4755 (suid)
  293. X  nn etc:        storm    other    755
  294. X
  295. X
  296. X         EXPIRED ARTICLES STILL SHOW UP ON THE MENUS
  297. X         -------------------------------------------
  298. X
  299. XThis happens if expiration has not been performed on the database
  300. Xafter expire has been run on the news system (or NNTP server).
  301. X
  302. XTo run expire on the database regularly, inserting the following
  303. Xcommand in the crontab to be executed at a suitable time (with
  304. Xpermissions of the owner of nnmaster).  Preferably it should run
  305. Ximmediately after normal expire has *completed*:
  306. X    /usr/local/bin/nnadmin =EYW
  307. X
  308. XThis expire is relatively cheap on a local system (< 10 minutes), but
  309. Xvia NNTP, it may be more expensive, so there it is recommended to only
  310. Xrun once a week or so.  I have some directions for a patch to the NNTP
  311. Xserver which will cure this problem -- see NNTP and nntp.c!
  312. X
  313. X
  314. X             ABOUT BLOCKED GROUPS
  315. X             --------------------
  316. X
  317. XA group is blocked while the nnmaster is collecting new articles in
  318. Xthat group.  In a newly initialized database, all groups will be
  319. Xblocked until the nnmaster has collected them the first time, which
  320. Xmay take an hour or so the first time you run nnmaster -r.
  321. X
  322. XThis means that there may not be any news to read for a while after
  323. Xyou have just started the nnmaster the first time.
  324. X
  325. XIf you run nnmaster with only a selection of groups on the command
  326. Xline, you may also see some blocked groups among the groups that are
  327. Xnot collected.
  328. X
  329. X
  330. X            PROBLEMS SENDING MAIL
  331. X            ---------------------
  332. X
  333. XSome people have experienced problems sending mail.
  334. X
  335. XThe unfortunate thing about this is that the report about the problems
  336. Xis sent to the user *by mail* - sort of catch-22.
  337. X
  338. XThe most common cause of these problems is that the definition of
  339. XREC_MAIL in config.h is not correct.  Either it is an invalid path or
  340. Xprogram name, or the command does not read its standard input to
  341. Xget the letter (for example, MMDF's `post' command wants the letter in
  342. Xa file).  The 'inst' script will check that the REC_MAIL and INEWS
  343. Xprograms exist, but not that they actually work.
  344. X
  345. XIn another case, uux was not silent causing the trace file (in the aux
  346. Xscript) to be non-empty which fooled nn to think that nothing had been
  347. Xsent (although it did).  If you get failed reply/follow-up messages
  348. Xlook for a line saying something like "uucp job XXXXX" - This is
  349. Xcaused by the environment setting JOBNO=ON.
  350. X
  351. XIn one case, the recmail program was corrupted.
  352. X
  353. XAlso notice that some recmail programs may treat a line consisting of
  354. Xa single period in the first position as end-of-file.
  355. X
  356. XIf you don't have any program which can be used as recmail, there is
  357. Xone possible candidate in the contrib/ directory.
  358. X
  359. X
  360. X               ACCESSING NEWS REMOTELY
  361. X               -----------------------
  362. X
  363. XI have received the following problem description which seems to
  364. Xindicate a network problem.  You can now set the variable
  365. X    retry-on-error
  366. Xto the number of times nn should try to open an article (you may
  367. Xwant to do this in the global init file!):
  368. X
  369. X   We are running on a VAXstation 2000, with the news accessed
  370. X   remotely, so I get a lot of "can't read" errors.  It seems to
  371. X   me these errors should ALWAYS require acknowledgement before
  372. X   clearing the message, and should offer the possibility to
  373. X   re-try the operation (which usually then works for us).
  374. X
  375. XThere is a similar option [-y] to nnmaster which can be set to have
  376. Xthe nnmaster perform retries as well.
  377. X
  378. X
  379. X            GROUPS BECOME OR REMAIN EMPTY
  380. X            -----------------------------
  381. X
  382. XIt has been observed on a few occations that some groups are not
  383. Xcollected correctly.  We have seldom managed to track down the actual
  384. Xproblem, but in practically all cases, NNTP and/or NFS has been
  385. Xinvolved, either accessing the news spool files remotely, or nnmaster
  386. Xupdating the database on another machine, or both.
  387. X
  388. XIt is very much recommended that nnmaster runs on the system when the
  389. Xdatabase is placed, and if at all possible that this is also the
  390. Xsystem on which the news spool files are located.  Furthermore, this
  391. Xis by far the most efficient way to run things.
  392. X
  393. XIn any case, don't hesistate to tell me if you see this problem with
  394. Xrelease 6.4.
  395. X
  396. X
  397. X            TERMINAL I-O PROBLEMS
  398. X            ---------------------
  399. X
  400. Xnn does not echo the characters you type except when you are entering
  401. Xa string, e.g. a file name.  Instead it attempts to make a significant
  402. Xchange to the data displayed on the screen.  On a slow system this may
  403. Xbe seen as a drawback; on fast systems it is an intended feature!
  404. X
  405. XIf CBREAK is available, nn will use it, but when CBREAK is not avaiable
  406. Xnn uses raw mode when reading from the keyboard and cooked mode when
  407. Xprinting on the screen (it flips forth and back).  [This behaviour can
  408. Xbe disabled by unsetting the flow-control variable]  This has caused
  409. Xproblems on some systems (e.g. the 3B2) where the tty driver is
  410. Xlocated on a dedicated IO-processor, which for some reason handles
  411. Xioctl's "out of band".  I have tried to work around these problems by
  412. Xoutputting \r\n sequences where \n should have been sufficient.
  413. X
  414. Xnn also intentionally discards type-ahead at certain points, but only
  415. Xif CBREAK mode is not supported, and the flow-control variable is set.
  416. X
  417. XOn some systems, TCSETAF also flushes the output queue; you may try to
  418. Xreplace it by TCSETAW followed by TCFLSH.
  419. X
  420. XOn terminals where the arrow keys send single characters, nn will not
  421. Xrecognize the arrow keys as arrows if they send a character which is
  422. Xalready recognized by nn for another purpose.  For example, on the
  423. Xwyse 50, the left arrow key sends the same code as the backspace key
  424. Xwhich is the default erase-key.  Therefore, the left arrow key will be
  425. Xinterpreted as the erase-key.  (This is contrary to 6.3 where the
  426. Xnormal function of the erase-key was - erroneously - inhibited and
  427. Xinterpreted as the arrow key).
  428. X
  429. X
  430. X                Cnews
  431. X                -----
  432. X
  433. Xnn wants articles to contain Lines: headers, but Cnews doesn't
  434. Xgenerate these in the default setup.  You may uncomment the
  435. XLines: code in the inews script.
  436. X
  437. XThe requirement for Cnews to update the 'min' field in the active
  438. Xfield has been removed in release 6.4.
  439. X
  440. X
  441. X        NNMASTER WILL NOT START OR IS LOOPING
  442. X        -------------------------------------
  443. X
  444. XIf no nnmaster is running, and nnmaster refuses to start up, you
  445. Xshould check for the existence of the MPID file in the LIB directory,
  446. XIf it exists, it should be removed.
  447. X
  448. XIf nnmaster starts looping, you should check the permissions on the
  449. XLIB directory and notice if a GATE file exists which nnmaster is not
  450. Xallowed to unlink.
  451. X
  452. X
  453. X            NNMASTER DIES RANDOMLY
  454. X            ----------------------
  455. X
  456. XThe definition DETATCH_TERMINAL in the s- file you use may not work
  457. X(it is a no-op on some systems).  This will cause a hangup signal to
  458. Xbe sent to the master when you logout, and that will terminate the
  459. Xmaster.  This may explain why the nnmaster seems to die for no
  460. Xapparant reason!
  461. X
  462. XIf nnmaster accesses news via NNTP, you should be aware of an NNTP
  463. Xlimitation which may cause problems to the nnmaster:  Groups with more
  464. Xthan 4096 articles may cause the nntp server to crash!   The easiest
  465. Xway to circumvent this problem is run expire on the large groups more
  466. Xfrequently.  (Thanks to Scott Hankin for this information).
  467. X
  468. X
  469. X             WARNINGS DURING COMPILATION
  470. X             ---------------------------
  471. X
  472. XIf you get a syntax error when compiling the folder.c file, you
  473. Xprobably have defined HAVE_DIRECTORY in the s- file, but even though
  474. Xthe include file exists, it does not define the DIR type.  Either get
  475. Xhold of a public domain directory package (look in the gnu
  476. Xdistribution), or just undefine HAVE_DIRECTORY which causes nn to use
  477. X(much slower) shell commands for file name completion (and disables
  478. Xthe ?-help for file names).
  479. X
  480. XIf the linker complains about not finding the function `strcspn'
  481. X(which should be in most standard libraries these days), define the
  482. Xsymbol STRCSPN in the m- file (or config.h) to use the version in
  483. Xregexp.c.
  484. X
  485. X
  486. X            FORMATTING THE MANUALS
  487. X            ----------------------
  488. X
  489. XMany versions of the -man package may have problems handling the `@'
  490. Xcharacters as hanging tags (.TP).  Fix your man package by
  491. Xsubstituting ALL occurrences of the @ character in tmac.an (or perhaps
  492. Xtmac.an.new) by a BEL (^G) character.
  493. X
  494. X
  495. X                   RESIZING
  496. X                   --------
  497. X
  498. XResizing only works with termcap (on BSD systems)!  It may also work
  499. Xon System V, but then you will probably also have to define the symbol
  500. XSYSV_RESIZING in config.h (see term.c).
  501. X
  502. XIf resizing occurs while reading an article, the article is repositioned
  503. Xon the first page of the article.
  504. X
  505. X
  506. X        NNMASTER AND NN DOES NOT FIND ANY NEWS
  507. X        --------------------------------------
  508. X
  509. XAll known occurrences of this problem have been identified and fixed,
  510. Xor somehow "explained" as being a NFS networking problem.
  511. X
  512. XBe careful about the 'limit' and 'old' variables.  Setting them in the
  513. Xinit file may cause nn to behave strangely (as documented :-)
  514. X
  515. X
  516. X    THE DATABASE BECOMES CORRUPTED FOR NO APPARENT REASON
  517. X    -----------------------------------------------------
  518. X
  519. XThis has been seen on some systems in the past.
  520. X
  521. X- The expire problem in release 6.3 has been fixed, since the code
  522. X  to perform the expiration has been rewritten and optimized.  No
  523. X  known bugs exist in this code.
  524. X
  525. X- Some .o files had not been recompiled by make after modifying the
  526. X  config.h file (this happened on SunOS 4.0 which seems to forget to
  527. X  update file modification times for some files (has anybody seen this
  528. X  before?)).
  529. X
  530. X
  531. X                8 BIT SUPPORT
  532. X                -------------
  533. X
  534. XI am rather embarrassed to admit that a program leaving Denmark in
  535. X1990 still does not fully support 8 bit character sets - however, that
  536. Xis the plain truth.  Currently, all characters typed on the keyboard
  537. Xare stripped to 7 bits, but articles displayed on the screen can be
  538. Xshown either in 7 or 8 bit mode via the data-bits variable.
  539. X
  540. X
  541. X                 MAIL RECORDS
  542. X                 ------------
  543. X
  544. XThere are some things you have to consider in connection with the mail
  545. Xand news record files:
  546. X
  547. X - When you :mail to yourself, a copy is not saved in the mail-record.
  548. X
  549. X - If the mail/post fails, the message is saved in ~/dead.letter instead
  550. X
  551. X - Since the posting is performed in the background and it may take
  552. X   upto a minute to complete posting an article, updating news-record
  553. X   will not happen instantly.
  554. X
  555. X - In previous releases the mail header created by nn in the record
  556. X   files are not recognized by the digest splitting code in nn, i.e.
  557. X   they always appear to contain a single article.  This was fixed in
  558. X   release 6.3, but you may have some very old folders which nn will
  559. X   not be able to split (there is no From: line).
  560. X
  561. X
  562. X             OLD AWK vs. NEW AWK
  563. X             -------------------
  564. X
  565. XSome scripts will not run with 'new awk'.  If your system's `awk' is
  566. Xthe new awk, you must define the symbol OLD_AWK to the path name of
  567. Xthe old awk program (normally `oawk').  See s-template.h.
  568. X
  569. X
  570. X                NN ON APOLLOS
  571. X                -------------
  572. X
  573. XApollo's C-preprocessor may not make a working ymakefile, because it
  574. Xmay convert leading tabs to spaces, and thus confusing make.  You'll
  575. Xhave to reconstruct the tabs somehow, e.g. using something like
  576. X    sed 's/^  */TAB/' < ymakefile > ...    (not tested!!)
  577. X
  578. XAlso notice that releases prior to SR10 will not handle directories
  579. Xwith more than 1300 entries, thus giving problems if you have more
  580. Xthan 650 groups in the database directory (d+x file).  Leaving
  581. XDB_DIRECTORY undefined should fix this.
  582. X
  583. X
  584. X                OTHER PROBLEMS
  585. X                --------------
  586. X
  587. XThe 'master flags' set on a group with nnadmin are forgotten if the
  588. Xdatabase is reinitialized with nnmaster -I.  To set these flags
  589. Xpermanently, you should set them in the GROUPS file.
  590. END_OF_FILE
  591.   if test 13534 -ne `wc -c <'doc/PROBLEMS'`; then
  592.     echo shar: \"'doc/PROBLEMS'\" unpacked with wrong size!
  593.   fi
  594.   # end of 'doc/PROBLEMS'
  595. fi
  596. if test -f 'expire.c' -a "${1}" != "-c" ; then 
  597.   echo shar: Will not clobber existing file \"'expire.c'\"
  598. else
  599.   echo shar: Extracting \"'expire.c'\" \(14104 characters\)
  600.   sed "s/^X//" >'expire.c' <<'END_OF_FILE'
  601. X/*
  602. X *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  603. X *
  604. X *    Expire will remove all entries in the index and data files
  605. X *    corresponding to the articles before the first article registered
  606. X *    in the active file.  No attempt is made to eliminate other
  607. X *    expired articles.
  608. X */
  609. X
  610. X#include "config.h"
  611. X#include "db.h"
  612. X#include "dir.h"
  613. X
  614. Ximport int trace, debug_mode;
  615. Ximport int nntp_failed;
  616. X
  617. X/*
  618. X *    Expire methods:
  619. X *    1: read directory and reuse database info.
  620. X *    2: "slide" index and datafiles (may leave unexpired art. in database)
  621. X *    3: recollect group to expire (also if "min" still exists)
  622. X */
  623. X
  624. Xexport expire_method = 1;    /* expire method */
  625. Xexport recollect_method = 1;    /* recollection method -- see do_expire */
  626. Xexport expire_level = 0;    /* automatic expiration detection */
  627. X
  628. X#ifdef HAVE_DIRECTORY
  629. X
  630. Xstatic article_number *article_list = NULL;
  631. Xstatic long art_list_length = 0;
  632. X
  633. Xstatic sort_art_list(f1, f2)
  634. Xregister article_number *f1, *f2;
  635. X{
  636. X    return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
  637. X}
  638. X
  639. Xstatic article_number *get_article_list(dir)
  640. Xchar *dir;
  641. X{
  642. X    DIR *dirp;
  643. X    register Direntry *dp;
  644. X    register char c, *pp, *cp;
  645. X    register article_number *art;
  646. X    register long count = 0;    /* No. of completions plus one */
  647. X
  648. X    if ((dirp = opendir(dir)) == NULL)
  649. X    return NULL;            /* tough luck */
  650. X
  651. X    art = article_list;
  652. X    count = 0;
  653. X
  654. X    while ((dp = readdir(dirp)) != NULL) {
  655. X    cp = dp->d_name;
  656. X#ifdef FAKED_DIRECTORY
  657. X    if (dp->d_ino == 0) continue;
  658. X    cp[14] = NUL;
  659. X#endif
  660. X    for (pp = cp; c = *pp++; )
  661. X        if (!isascii(c) || !isdigit(c)) break;
  662. X    if (c) continue;
  663. X
  664. X    if (count == art_list_length) {
  665. X        art_list_length += 250;
  666. X        article_list = resizeobj(article_list, article_number, art_list_length + 1);
  667. X        art = article_list + count;
  668. X    }
  669. X    *art++ = atol(cp);
  670. X    count++;
  671. X    }
  672. X    closedir(dirp);
  673. X    if (count)
  674. X    quicksort(article_list, count, article_number, sort_art_list);
  675. X    *art = 0;
  676. X
  677. X    return article_list;
  678. X}
  679. X
  680. Xstatic long expire_in_database(gh)
  681. Xregister group_header *gh;
  682. X{
  683. X    FILE *old, *data, *ix;
  684. X    off_t old_max_offset;
  685. X    register article_number *list;
  686. X#ifdef NNTP
  687. X    extern article_number *nntp_get_article_list();
  688. X#endif
  689. X    article_number old_last_article;
  690. X    long count;
  691. X
  692. X    if (gh->first_db_article > gh->last_db_article) return 0;
  693. X
  694. X    if (!init_group(gh)) return 0;
  695. X
  696. X    if (debug_mode == 1) {
  697. X    printf("\t\tExp %s (%ld..%ld)\r",
  698. X           gh->group_name, gh->first_db_article, gh->last_db_article);
  699. X    fl;
  700. X    }
  701. X
  702. X    count = 0;
  703. X    old = data = ix = NULL;
  704. X
  705. X    /* get list of currently available articles in the group */
  706. X#ifdef NNTP
  707. X    if (use_nntp)
  708. X    list = nntp_get_article_list(gh);
  709. X    else
  710. X#endif
  711. X    list = get_article_list(".");
  712. X
  713. X    if (list == NULL || *list == 0) {
  714. X#ifdef NNTP
  715. X    if (nntp_failed == 2) {
  716. X        log_entry('N', "NNTP server supports neither LISTGROUP nor XHDR");
  717. X        sys_error("Cannot use specified expire method (NNTP limitations)");
  718. X    }
  719. X    if (nntp_failed) return -1;
  720. X#endif
  721. X    if (debug_mode == 1) { printf("\rempty"); fl; }
  722. X    /* group is empty - clean it */
  723. X    count = gh->last_db_article - gh->first_db_article + 1;
  724. X    clean_group(gh);
  725. X    gh->last_db_article = gh->last_a_article;
  726. X    gh->first_db_article = gh->last_db_article + 1;
  727. X    db_write_group(gh);
  728. X    return count;
  729. X    }
  730. X
  731. X    /*
  732. X     * Clean & block the group while expire is working on it.
  733. X     */
  734. X
  735. X    gh->first_db_article = 0;
  736. X    old_last_article = gh->last_db_article;
  737. X    gh->last_db_article = 0;
  738. X
  739. X    gh->index_write_offset = (off_t)0;
  740. X    old_max_offset = gh->data_write_offset;
  741. X    gh->data_write_offset = (off_t)0;
  742. X
  743. X    gh->master_flag &= ~M_EXPIRE;
  744. X    gh->master_flag |= M_BLOCKED;
  745. X
  746. X    db_write_group(gh);
  747. X
  748. X    /*
  749. X     * We ignore the old index file, and we unlink the data file
  750. X     * immediately after open because we want to write a new.
  751. X     */
  752. X
  753. X    (void)open_data_file(gh, 'x', -1);
  754. X    old = open_data_file(gh, 'd', OPEN_READ | OPEN_UNLINK);
  755. X    if (old == NULL) goto out;
  756. X
  757. X    data = open_data_file(gh, 'd', OPEN_CREATE | MUST_EXIST);
  758. X    ix = open_data_file(gh, 'x', OPEN_CREATE | MUST_EXIST);
  759. X
  760. X    while (ftell(old) < old_max_offset) {
  761. X    if (s_hangup) {
  762. X        /* ok, this is what we got -- let collect get the rest */
  763. X        old_last_article = gh->last_db_article;
  764. X        break;
  765. X    }
  766. X
  767. X    /*
  768. X     * maybe not enough articles, or last one is incomplete
  769. X     * we take what there is, and leave the rest to do_collect()
  770. X     * It may actually be ok if the last articles have been expired/
  771. X     * cancelled!
  772. X     */
  773. X
  774. X    if (db_read_art(old) <= 0) {
  775. X        break;
  776. X    }
  777. X
  778. X    if (debug_mode == 1) { printf("\r%ld", (long)db_hdr.dh_number); fl; }
  779. X
  780. X    /* check whether we want this article */
  781. X
  782. X    while (*list && db_hdr.dh_number > *list) {
  783. X        /* potentially, we have a problem here: there might be an */
  784. X        /* article in the directory which is not in the database! */
  785. X        /* For the moment we just ignore this - it might be a bad */
  786. X        /* article which has been rejected!! */
  787. X        list++;
  788. X    }
  789. X
  790. X    if (*list == 0) {
  791. X        /* no more articles in the directory - the rest must be */
  792. X        /* expired.  So we ignore the rest of the data file */
  793. X        break;
  794. X    }
  795. X
  796. X    if (db_hdr.dh_number < *list) {
  797. X        /* the current article from the data file isn't in the */
  798. X        /* article list, so it must be expired! */
  799. X        count++;
  800. X        if (debug_mode == 1) { printf("\t%ld", count); fl; }
  801. X        continue;
  802. X    }
  803. X
  804. X    if (gh->first_db_article == 0) {
  805. X        gh->first_db_article = db_hdr.dh_number;
  806. X        gh->last_db_article = db_hdr.dh_number - 1;
  807. X    }
  808. X
  809. X    if (gh->last_db_article < db_hdr.dh_number) {
  810. X        gh->data_write_offset = ftell(data);
  811. X
  812. X        /* must fill gab between last index and current article */
  813. X        while (gh->last_db_article < db_hdr.dh_number) {
  814. X        if (!db_write_offset(ix, &(gh->data_write_offset)))
  815. X            write_error();
  816. X        gh->last_db_article++;
  817. X        }
  818. X    }
  819. X
  820. X    if (db_write_art(data) < 0) write_error();
  821. X    }
  822. X
  823. X    if (gh->first_db_article == 0) {
  824. X    gh->first_db_article = old_last_article + 1;
  825. X    gh->last_db_article = old_last_article;
  826. X    } else {
  827. X    gh->data_write_offset = ftell(data);
  828. X    while (gh->last_db_article < old_last_article) {
  829. X        /* must fill gab between last index and last article */
  830. X        ++gh->last_db_article;
  831. X        if (!db_write_offset(ix, &(gh->data_write_offset)))
  832. X        write_error();
  833. X    }
  834. X    gh->index_write_offset = ftell(ix);
  835. X    }
  836. X
  837. X    gh->master_flag &= ~M_BLOCKED;
  838. X
  839. X    db_write_group(gh);
  840. X
  841. X out:
  842. X    if (old) fclose(old);
  843. X    if (data) fclose(data);
  844. X    if (ix) fclose(ix);
  845. X
  846. X    if (debug_mode) putchar(NL);
  847. X
  848. X    return count;
  849. X}
  850. X#else
  851. X#define expire_in_database expire_sliding
  852. X#endif
  853. X
  854. Xstatic long expire_sliding(gh)
  855. Xregister group_header *gh;
  856. X{
  857. X    FILE *old_x, *old_d;
  858. X    FILE *new;
  859. X    off_t index_offset, data_offset, new_offset;
  860. X    long count, expire_count;
  861. X    char *err_message;
  862. X
  863. X#define expire_error(msg) { \
  864. X    err_message = msg; \
  865. X    goto error_handler; \
  866. X}
  867. X
  868. X    old_x = old_d = new = NULL;
  869. X
  870. X#ifdef RENUMBER_DANGER
  871. X    /*
  872. X     * check whether new first article is collected
  873. X     */
  874. X
  875. X    if (!art_collected(gh, gh->first_a_article)) {
  876. X    expire_count = gh->first_db_article - gh->last_db_article + 1;
  877. X    err_message = NULL;
  878. X    goto error_handler;    /* renumbering, collect from start */
  879. X    }
  880. X#else
  881. X    if (gh->first_a_article <= gh->first_db_article)
  882. X    return 0;
  883. X#endif
  884. X
  885. X    expire_count = gh->first_a_article - gh->first_db_article;
  886. X
  887. X    new = NULL;
  888. X
  889. X    /*
  890. X     *  Open old files, unlink after open
  891. X     */
  892. X
  893. X    old_x = open_data_file(gh, 'x', OPEN_READ|OPEN_UNLINK);
  894. X    old_d = open_data_file(gh, 'd', OPEN_READ|OPEN_UNLINK);
  895. X
  896. X    if (old_x == NULL || old_d == NULL)
  897. X    expire_error("INDEX or DATA file missing");
  898. X
  899. X    /*
  900. X     *    Create new index file; copy from old
  901. X     */
  902. X
  903. X    new = open_data_file(gh, 'x', OPEN_CREATE);
  904. X    if (new == NULL)
  905. X    expire_error("INDEX: cannot create");
  906. X
  907. X    /*
  908. X     *    index_offset is the offset into the old index file for the
  909. X     *    first entry in the new index file
  910. X     */
  911. X
  912. X    index_offset = get_index_offset(gh, gh->first_a_article);
  913. X
  914. X    /*
  915. X     *    adjust the group's index write offset (the next free entry)
  916. X     */
  917. X
  918. X    gh->index_write_offset -= index_offset;
  919. X
  920. X    /*
  921. X     *    calculate the number of entries to copy
  922. X     */
  923. X
  924. X    count = gh->index_write_offset / sizeof(off_t);
  925. X
  926. X    /*
  927. X     *    data offset is the offset into the old data file for the
  928. X     *    first byte in the new data file; it is initialized in the
  929. X     *    loop below, by reading the entry in the old index file at
  930. X     *    offset 'index_offset'.
  931. X     */
  932. X
  933. X    data_offset = (off_t)0;
  934. X
  935. X    /*
  936. X     *    read 'count' entries from the old index file starting from
  937. X     *    index_offset, subtract the 'data_offset', and output the
  938. X     *    new offset to the new index file.
  939. X     */
  940. X
  941. X    fseek(old_x, index_offset, 0);
  942. X
  943. X    while (--count >= 0) {
  944. X    if (!db_read_offset(old_x, &new_offset))
  945. X        expire_error("INDEX: too short");
  946. X
  947. X    if (data_offset == (off_t)0) data_offset = new_offset;
  948. X
  949. X    new_offset -= data_offset;
  950. X    if (!db_write_offset(new, &new_offset))
  951. X        expire_error("NEW INDEX: cannot write");
  952. X    }
  953. X
  954. X    fclose(new);
  955. X    fclose(old_x); old_x = NULL;
  956. X
  957. X    /*
  958. X     *    copy from old data file to new data file
  959. X     */
  960. X
  961. X    new = open_data_file(gh, 'd', OPEN_CREATE);
  962. X    if (new == NULL)
  963. X    expire_error("DATA: cannot create");
  964. X
  965. X    /*
  966. X     *    calculate offset for next free entry in the new data file
  967. X     */
  968. X
  969. X    gh->data_write_offset -= data_offset;
  970. X
  971. X    /*
  972. X     *    calculate number of bytes to copy (piece of cake)
  973. X     */
  974. X
  975. X    count = gh->data_write_offset;
  976. X
  977. X    /*
  978. X     *    copy 'count' bytes from the old data file, starting at offset
  979. X     *     'data_offset', to the new data file
  980. X     */
  981. X
  982. X    fseek(old_d, data_offset, 0);
  983. X    while (count > 0) {
  984. X    char block[1024];
  985. X    int  count1;
  986. X
  987. X    count1 = fread(block, sizeof(char), 1024, old_d);
  988. X    if (count1 <= 0)
  989. X        expire_error("DATA: read error");
  990. X
  991. X    if (fwrite(block, sizeof(char), count1, new) != count1)
  992. X        expire_error("DATA: write error");
  993. X
  994. X    count -= count1;
  995. X    }
  996. X
  997. X    fclose(new);
  998. X    fclose(old_d);
  999. X
  1000. X    /*
  1001. X     *    Update group entry
  1002. X     */
  1003. X
  1004. X    gh->first_db_article = gh->first_a_article;
  1005. X
  1006. X    /*
  1007. X     *    Return number of expired articles
  1008. X     */
  1009. X
  1010. X    return expire_count;
  1011. X
  1012. X    /*
  1013. X     *    Errors end up here.
  1014. X     *    We simply recollect the whole group once more.
  1015. X     */
  1016. X
  1017. Xerror_handler:
  1018. X
  1019. X    if (new) fclose(new);
  1020. X    if (old_x) fclose(old_x);
  1021. X    if (old_d) fclose(old_d);
  1022. X
  1023. X    if (err_message)
  1024. X    log_entry('E', "Expire Error (%s): %s", gh->group_name, err_message);
  1025. X
  1026. X    clean_group(gh);
  1027. X
  1028. X    /* will be saved & unblocked later */
  1029. X
  1030. X    /*
  1031. X     *    We cannot say whether any articles actually had to be expired,
  1032. X     *    but then we must guess...
  1033. X     */
  1034. X
  1035. X    return expire_count;
  1036. X}
  1037. X
  1038. Xstatic block_group(gh)
  1039. Xregister group_header *gh;
  1040. X{
  1041. X    if ((gh->master_flag & M_BLOCKED) == 0) {
  1042. X    gh->master_flag |= M_BLOCKED;
  1043. X    db_write_group(gh);
  1044. X    }
  1045. X}
  1046. X
  1047. Xstatic unblock_group(gh)
  1048. Xregister group_header *gh;
  1049. X{
  1050. X    if (gh->master_flag & M_BLOCKED) {
  1051. X    gh->master_flag &= ~(M_BLOCKED | M_EXPIRE);
  1052. X    db_write_group(gh);
  1053. X    }
  1054. X}
  1055. X
  1056. Xdo_expire()
  1057. X{
  1058. X    register group_header *gh;
  1059. X    long exp_article_count, temp;
  1060. X    int exp_group_count, must_expire;
  1061. X    time_t start_time;
  1062. X
  1063. X    must_expire = 0;
  1064. X
  1065. X    Loop_Groups_Header(gh) {
  1066. X    if (s_hangup) break;
  1067. X    if (gh->master_flag & M_IGNORE_GROUP) continue;
  1068. X
  1069. X    if ((gh->master_flag & M_VALID) == 0) {
  1070. X        if (gh->last_db_article == 0) continue;
  1071. X        log_entry('X', "Group %s removed", gh->group_name);
  1072. X        gh->master_flag |= M_IGNORE_A;
  1073. X        clean_group(gh);
  1074. X        continue;
  1075. X    }
  1076. X
  1077. X#ifdef RENUMBER_DANGER
  1078. X    if (gh->last_db_article  > gh->last_a_article ||
  1079. X        gh->first_db_article > gh->first_a_article) {
  1080. X        log_entry('X', "group %s renumbered", gh->group_name);
  1081. X        clean_group(gh);
  1082. X        continue;
  1083. X    }
  1084. X#endif
  1085. X
  1086. X    if (gh->master_flag & M_AUTO_RECOLLECT) {
  1087. X        switch (recollect_method) {
  1088. X         case 1: /* expire when new articles arrive */
  1089. X        if (gh->last_a_article <= gh->last_db_article) break;
  1090. X         case 2: /* expire unconditionally */
  1091. X        gh->master_flag |= M_EXPIRE;
  1092. X        must_expire = 1;
  1093. X        continue;
  1094. X
  1095. X         case 3: /* clean when new articles arrive */
  1096. X        if (gh->last_a_article <= gh->last_db_article) break;
  1097. X         case 4: /* clean unconditionally */
  1098. X        gh->master_flag |= M_MUST_CLEAN;
  1099. X        continue;
  1100. X         default:        /* ignore auto-recollect */
  1101. X        break;
  1102. X        }
  1103. X    }
  1104. X
  1105. X    if (gh->index_write_offset > 0) {
  1106. X        if (gh->first_a_article > gh->last_db_article) {
  1107. X        if (trace)
  1108. X            log_entry('T', "%s expire void", gh->group_name);
  1109. X        if (debug_mode)
  1110. X            printf("%s expire void\n", gh->group_name);
  1111. X        clean_group(gh);
  1112. X        continue;
  1113. X        }
  1114. X    }
  1115. X
  1116. X    if (gh->master_flag & M_EXPIRE) {
  1117. X        must_expire = 1;
  1118. X        continue;
  1119. X    }
  1120. X
  1121. X    if (expire_level > 0 &&
  1122. X        (gh->first_db_article + expire_level) <= gh->first_a_article) {
  1123. X        if (trace)
  1124. X        log_entry('T', "%s expire level", gh->group_name);
  1125. X        if (debug_mode)
  1126. X        printf("Expire level: %s\n", gh->group_name);
  1127. X        gh->master_flag |= M_EXPIRE;
  1128. X        must_expire = 1;
  1129. X        continue;
  1130. X    }
  1131. X    }
  1132. X
  1133. X    if (!must_expire) return 1;
  1134. X
  1135. X    start_time = cur_time();
  1136. X    exp_article_count = exp_group_count = 0;
  1137. X    temp = 0;
  1138. X
  1139. X    Loop_Groups_Header(gh) {
  1140. X    if (s_hangup) {
  1141. X        temp = -1;
  1142. X        break;
  1143. X    }
  1144. X
  1145. X    if (gh->master_flag & M_IGNORE_GROUP) continue;
  1146. X    if ((gh->master_flag & M_EXPIRE) == 0) continue;
  1147. X    if (gh->master_flag & M_MUST_CLEAN) continue;
  1148. X
  1149. X    if (trace)
  1150. X        log_entry('T', "Exp %s (%ld -> %ld)", gh->group_name,
  1151. X              (long)gh->first_db_article, (long)gh->first_a_article);
  1152. X
  1153. X    switch (expire_method) {
  1154. X     case 1:
  1155. X        block_group(gh);
  1156. X        temp = expire_in_database(gh);
  1157. X        unblock_group(gh);
  1158. X        break;
  1159. X
  1160. X     case 2:
  1161. X        block_group(gh);
  1162. X        temp = expire_sliding(gh);
  1163. X        unblock_group(gh);
  1164. X        break;
  1165. X
  1166. X     case 4:
  1167. X        temp = gh->first_a_article - gh->first_db_article;
  1168. X        if (temp <= 0) break;
  1169. X     case 3:
  1170. X        gh->master_flag |= M_MUST_CLEAN;
  1171. X        break;
  1172. X
  1173. X    }
  1174. X
  1175. X#ifdef NNTP
  1176. X    if (nntp_failed) {
  1177. X        /* connection broken while reading article list */
  1178. X        /* so no harm was done - leave the group to be expired */
  1179. X        /* again on next expire */
  1180. X        break;
  1181. X    }
  1182. X#endif
  1183. X
  1184. X    if (temp > 0) {
  1185. X        exp_article_count += temp;
  1186. X        exp_group_count++;
  1187. X    }
  1188. X    }
  1189. X
  1190. X    if (exp_article_count > 0)
  1191. X    log_entry('X', "Expire: %ld art, %d gr, %ld s",
  1192. X          exp_article_count, exp_group_count,
  1193. X          (long)(cur_time() - start_time));
  1194. X
  1195. X    return temp >= 0;
  1196. X}
  1197. END_OF_FILE
  1198.   if test 14104 -ne `wc -c <'expire.c'`; then
  1199.     echo shar: \"'expire.c'\" unpacked with wrong size!
  1200.   fi
  1201.   # end of 'expire.c'
  1202. fi
  1203. if test -f 'save.c' -a "${1}" != "-c" ; then 
  1204.   echo shar: Will not clobber existing file \"'save.c'\"
  1205. else
  1206.   echo shar: Extracting \"'save.c'\" \(16732 characters\)
  1207.   sed "s/^X//" >'save.c' <<'END_OF_FILE'
  1208. X/*
  1209. X *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  1210. X *
  1211. X *    Save (or print) articles
  1212. X */
  1213. X
  1214. X#include <signal.h>
  1215. X#include <errno.h>
  1216. X#include "config.h"
  1217. X#include "term.h"
  1218. X#include "keymap.h"
  1219. X#include "news.h"
  1220. X
  1221. Xexport char *default_save_file = "+$F";
  1222. Xexport int suggest_save_file = 1;
  1223. Xexport char *unshar_header_file = "Unshar.Headers";
  1224. Xexport int  use_mail_folders = 0;
  1225. Xexport int  use_mmdf_folders = 0;
  1226. Xexport int  save_report = 1;
  1227. Xexport int  quick_save = 0;
  1228. Xexport int  conf_append = 0;
  1229. Xexport int  conf_create = 1;
  1230. X
  1231. Xexport char *save_counter_format = "%d";    /* format of save counter */
  1232. Xexport int  save_counter_offset = 0;
  1233. X
  1234. Xexport char printer[FILENAME] = DEFAULT_PRINTER;
  1235. Xexport int edit_print_command = 1;
  1236. X
  1237. Xexport char patch_command[FILENAME] = "patch -p0";
  1238. Xexport int edit_patch_command = 1;
  1239. X
  1240. Xexport char unshar_command[FILENAME] = SHELL;
  1241. Xexport int edit_unshar_command = 0;
  1242. X
  1243. Xextern int file_completion();
  1244. X
  1245. Ximport char *temp_file;
  1246. Ximport int  shell_restrictions;
  1247. Ximport char delayed_msg[];
  1248. X
  1249. Ximport int rot13_active;
  1250. X
  1251. Xstatic int save_mode;
  1252. Xstatic char *unshar_cmd;
  1253. X
  1254. X#define    HEADER_HANDLING    0x0f    /* what should we do with the header */
  1255. X
  1256. X#define NO_HEADER    0    /* save without a header */
  1257. X#define    FULL_HEADER    1    /* save with full header */
  1258. X#define SHORT_HEADER    2    /* save with partial header */
  1259. X#define    SHORT_HEADER_DG    3    /* save with partial header (digest) */
  1260. X
  1261. X
  1262. X#define    SEPARATE_FILES    0x0100    /* save as separate files */
  1263. X#define UNIQUE_FILES    0x0200    /* save in unique files */
  1264. X#define    FILE_IS_NEW    0x0400    /* this is a new file */
  1265. X#define APPEND_ARTNUM    0x0800    /* append article number to file name */
  1266. X#define    IS_PIPE        0x1000    /* output is on pipe */
  1267. X#define    DO_UNSHAR    0x2000    /* unshar article (or patch) */
  1268. X#define    DO_PATCH    0x4000    /* patch article */
  1269. X#define    DO_DECODE    0x8000    /* uudecode article */
  1270. X
  1271. X/* open modes for open_news_article for the various HEADER_HANDLINGs */
  1272. X
  1273. Xstatic int open_mode[] = {
  1274. X    SKIP_HEADER,
  1275. X    0,
  1276. X    FILL_NEWS_HEADER | SKIP_HEADER,
  1277. X    FILL_DIGEST_HEADER | SKIP_HEADER
  1278. X};
  1279. X
  1280. Xstatic FILE *save_file;            /* output stream for saved files */
  1281. Xstatic char *save_name;            /* save file name */
  1282. X
  1283. Xstatic int uniq_counter;        /* separate files counter */
  1284. Xstatic char uniq_format[FILENAME];     /* sprintf format for '*' expansion */
  1285. X
  1286. Xstatic char last_dir[FILENAME] = "";
  1287. X
  1288. X/*
  1289. X * return pointer to first path name component, that does not exist
  1290. X */
  1291. X
  1292. Xstatic char *ckdir_path(name)
  1293. Xchar *name;
  1294. X{
  1295. X    char *slash;
  1296. X    char *component;
  1297. X
  1298. X    component = name;
  1299. X
  1300. X    while (slash = strchr(component, '/')) {
  1301. X    if (slash == component) {
  1302. X        /*  ...//...  */
  1303. X        component++;
  1304. X        continue;
  1305. X    }
  1306. X    *slash = NUL;
  1307. X    if (file_exist(name, "drx")) {
  1308. X        *slash++ = '/';
  1309. X        component = slash;
  1310. X        continue;
  1311. X    }
  1312. X    if (file_exist(name, (char *)NULL)) {
  1313. X        *slash = '/';
  1314. X        return NULL;
  1315. X    }
  1316. X    *slash = '/';
  1317. X    break;
  1318. X    }
  1319. X    return component;
  1320. X}
  1321. X
  1322. X/*
  1323. X * create directories in path name, starting from start
  1324. X */
  1325. X
  1326. Xstatic mkdirs_in_path(name, start)
  1327. Xchar *name, *start;
  1328. X{
  1329. X    char *slash;
  1330. X    char *component;
  1331. X
  1332. X    component = start;
  1333. X
  1334. X    while (slash = strchr(component, '/')) {
  1335. X    if (slash == component) {
  1336. X        /*  ...//...  */
  1337. X        component++;
  1338. X        continue;
  1339. X    }
  1340. X    *slash = NUL;
  1341. X    if (mkdir(name, 0755)) {
  1342. X        msg("Cannot make %s/", name);
  1343. X        *slash = '/';
  1344. X        return 0;
  1345. X    }
  1346. X    *slash++ = '/';
  1347. X    component = slash;
  1348. X    }
  1349. X    return 1;
  1350. X}
  1351. X
  1352. Xchar *init_save(command, mode_textp)
  1353. Xchar command;
  1354. Xchar **mode_textp;
  1355. X{
  1356. X    char *mode_text;
  1357. X    static char last_input[FILENAME] = "";
  1358. X    static char name_buf[512];    /* buffer for file name & command expansion */
  1359. X    char *start, *last, *np;
  1360. X    char *ckdir_path();
  1361. X
  1362. X    uniq_counter = 0;
  1363. X
  1364. X    switch (command) {
  1365. X
  1366. X     case K_SAVE_FULL_HEADER:
  1367. X    save_mode = FULL_HEADER;
  1368. X    mode_text = "Save";
  1369. X    goto cont_save;
  1370. X
  1371. X     case K_SAVE_SHORT_HEADER:
  1372. X    save_mode = SHORT_HEADER;
  1373. X    mode_text = "Output";
  1374. X    goto cont_save;
  1375. X
  1376. X     case K_SAVE_NO_HEADER:
  1377. X    save_mode = NO_HEADER;
  1378. X    mode_text = "Write";
  1379. X
  1380. X     cont_save:
  1381. X    if (quick_save && (current_group->group_flag & G_FOLDER) == 0) {
  1382. X        if (current_group->save_file)
  1383. X        save_name = current_group->save_file;
  1384. X        else
  1385. X        save_name = default_save_file;
  1386. X        strcpy(last_input, save_name);
  1387. X        save_name = last_input;
  1388. X    } else {
  1389. X        prompt("\1%s on\1 (+~|) ", mode_text);
  1390. X
  1391. X        save_name = current_group->save_file;
  1392. X        if (save_name == NULL && suggest_save_file)
  1393. X        save_name = default_save_file;
  1394. X        if (save_name != NULL) {
  1395. X        if (!expand_file_name(name_buf, save_name, 2)) return NULL;
  1396. X        save_name = name_buf;
  1397. X        }
  1398. X
  1399. X        save_name = get_s(last_input, save_name, NONE, file_completion);
  1400. X        if (save_name == NULL || *save_name == NUL) return NULL;
  1401. X
  1402. X        if (save_name[1] == NUL && save_name[0] == '+')
  1403. X        save_name = default_save_file;
  1404. X        else
  1405. X        if (current_group->save_file == NULL ||
  1406. X            strcmp(save_name, current_group->save_file))
  1407. X            strcpy(last_input, save_name);
  1408. X    }
  1409. X
  1410. X    if (*save_name == '|') {
  1411. X        if (shell_restrictions) {
  1412. X        msg("Restricted operation - pipes not allowed");
  1413. X        return NULL;
  1414. X        }
  1415. X        mode_text = "Pipe";
  1416. X        save_name++;
  1417. X        save_mode |= IS_PIPE;
  1418. X        if (*save_name == '|') {
  1419. X        save_mode |= SEPARATE_FILES;
  1420. X        save_name++;
  1421. X        }
  1422. X    }
  1423. X
  1424. X    if (!expand_file_name(name_buf, save_name, (save_mode & IS_PIPE) ? 11 : 3))
  1425. X        return NULL;
  1426. X    save_name = name_buf;
  1427. X
  1428. X    if ((save_mode & IS_PIPE) == 0) {
  1429. X        np = strrchr(save_name, '*');
  1430. X        if (np != NULL) {
  1431. X        *np = NUL;
  1432. X        sprintf(uniq_format, "%s%s%s",
  1433. X            save_name, save_counter_format, np + 1);
  1434. X        *np = '*';
  1435. X        save_mode |= SEPARATE_FILES | UNIQUE_FILES;
  1436. X        } else {
  1437. X        np = strrchr(save_name, '$');
  1438. X        if (np != NULL && np[1] == 'N') {
  1439. X            if (current_group->group_flag & G_FOLDER) {
  1440. X            msg("$N is not defined in a folder");
  1441. X            return NULL;
  1442. X            }
  1443. X            *np = NUL;
  1444. X            sprintf(uniq_format, "%s%%ld%s", save_name, np + 2);
  1445. X            *np = '$';
  1446. X            save_mode |= SEPARATE_FILES | APPEND_ARTNUM;
  1447. X        }
  1448. X        }
  1449. X    }
  1450. X    break;
  1451. X
  1452. X     case K_UUDECODE:
  1453. X    save_mode = NO_HEADER | DO_UNSHAR | DO_DECODE;
  1454. X    mode_text = "Decode";
  1455. X    goto unshar1;
  1456. X
  1457. X     case K_PATCH:
  1458. X    save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR | DO_PATCH;
  1459. X    mode_text = "Patch";
  1460. X    if (!shell_restrictions && edit_patch_command) {
  1461. X        prompt("\1Patch command:\1 ");
  1462. X        save_name = get_s(NONE, patch_command, NONE, NULL_FCT);
  1463. X        if (save_name == NULL || *save_name == NUL) return NULL;
  1464. X        strcpy(patch_command, save_name);
  1465. X    }
  1466. X    unshar_cmd = patch_command;
  1467. X    goto unshar1;
  1468. X
  1469. X     case K_UNSHAR:
  1470. X    save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR;
  1471. X    mode_text = "Unshar";
  1472. X    if (!shell_restrictions && edit_unshar_command) {
  1473. X        prompt("\1Unshar command:\1 ");
  1474. X        save_name = get_s(NONE, unshar_command, NONE, NULL_FCT);
  1475. X        if (save_name == NULL || *save_name == NUL) return NULL;
  1476. X        strcpy(unshar_command, save_name);
  1477. X    }
  1478. X    unshar_cmd = unshar_command;
  1479. X
  1480. X     unshar1:
  1481. X    prompt("\1%s Directory:\1 ", mode_text);
  1482. X    save_name = current_group->save_file;
  1483. X    if (save_name != NULL) {
  1484. X        if (!expand_file_name(name_buf, save_name, 10)) return NULL;
  1485. X        save_name = name_buf;
  1486. X    }
  1487. X
  1488. X    save_name = get_s(last_dir, save_name, NONE, file_completion);
  1489. X    if (save_name == NULL || *save_name == NUL) return NULL;
  1490. X    strcpy(last_dir, save_name);
  1491. X    if (!expand_file_name(name_buf, save_name, 1))
  1492. X        return NULL;
  1493. X    save_name = name_buf;
  1494. X    break;
  1495. X
  1496. X     case K_PRINT:
  1497. X    save_mode = SHORT_HEADER | IS_PIPE;
  1498. X
  1499. X    if (!shell_restrictions && edit_print_command) {
  1500. X        prompt("\1Print command:\1 ");
  1501. X        save_name = get_s(NONE, printer, NONE, NULL_FCT);
  1502. X        if (save_name == NULL || *save_name == NUL) return NULL;
  1503. X        strcpy(printer, save_name);
  1504. X    }
  1505. X    if (!expand_file_name(name_buf, printer, 1))
  1506. X        return NULL;
  1507. X    save_name = name_buf;
  1508. X    mode_text = "Print";
  1509. X    break;
  1510. X
  1511. X     default:
  1512. X    msg("Illegal save command: %d", command);
  1513. X    return NULL;
  1514. X    }
  1515. X
  1516. X    if (save_name == NULL) return NULL;
  1517. X
  1518. X    if (!(save_mode & IS_PIPE)) {
  1519. X    if (file_exist(save_name, (save_mode & DO_UNSHAR) ? "wd" : "wf")) {
  1520. X        if (save_mode & DO_UNSHAR) {
  1521. X        int len = strlen(save_name);
  1522. X        if (save_name[len - 1] != '/')
  1523. X            strcpy(save_name + len, "/");
  1524. X        } else
  1525. X        if (conf_append) {
  1526. X            printf("\rAppend to: %s ? ", save_name);
  1527. X            clrline();
  1528. X            if (!yes(0)) return NULL;
  1529. X        }
  1530. X    } else {
  1531. X        if (errno != ENOENT) {
  1532. X        msg("Cannot access %s", save_name);
  1533. X        return NULL;
  1534. X        }
  1535. X
  1536. X        if (save_mode & DO_UNSHAR) {
  1537. X        int len = strlen(save_name);
  1538. X        if (save_name[len - 1] != '/')
  1539. X            strcpy(save_name + len, "/");
  1540. X        }
  1541. X
  1542. X        start = ckdir_path(save_name);
  1543. X        if (start == NULL) {
  1544. X        msg("No permission");
  1545. X        return NULL;
  1546. X        }
  1547. X
  1548. X        last = strrchr(start, '/');
  1549. X        /* last != NULL => non-existing directories */
  1550. X
  1551. X        if (conf_create && (!(save_mode & SEPARATE_FILES) || last)) {
  1552. X        printf("\rCreate ");
  1553. X        for (np = save_name; *np; np++) {
  1554. X            if (np == start) putchar('\"');
  1555. X            putchar(*np);
  1556. X            if ((save_mode & SEPARATE_FILES) && np == last) break;
  1557. X        }
  1558. X        printf("\" ?");
  1559. X        clrline();
  1560. X        if (yes(last != NULL) <= 0) return NULL;
  1561. X        }
  1562. X
  1563. X        if (last && !mkdirs_in_path(save_name, start))
  1564. X        return NULL;
  1565. X    }
  1566. X    }
  1567. X
  1568. X    if (mode_textp) *mode_textp = mode_text;
  1569. X
  1570. X    save_mode |= FILE_IS_NEW;    /* so save() will open it */
  1571. X
  1572. X    if (save_mode & DO_DECODE) {
  1573. X    uud_start(save_name);
  1574. X    save_mode &= ~DO_UNSHAR;
  1575. X    }
  1576. X
  1577. X    return save_name;
  1578. X}
  1579. X
  1580. X
  1581. Xsave(ah)
  1582. Xarticle_header *ah;
  1583. X{
  1584. X    register FILE *art;
  1585. X    register c, lcount, mode;
  1586. X    news_header_buffer hdrbuf;
  1587. X    int was_raw = 0, set_visual = 0;
  1588. X    char copybuf[FILENAME * 4], uniqbuf[FILENAME];
  1589. X
  1590. X    if (ah->a_group) init_group(ah->a_group);
  1591. X
  1592. X    mode = save_mode & HEADER_HANDLING;
  1593. X    if (mode == SHORT_HEADER && ah->flag & A_DIGEST)
  1594. X    mode = SHORT_HEADER_DG;
  1595. X
  1596. X    art = open_news_article(ah, open_mode[mode], hdrbuf, (char *)NULL);
  1597. X    if (art == NULL) {
  1598. X    msg("Cannot read %s", group_path_name);
  1599. X    return 0;
  1600. X    }
  1601. X
  1602. X    if (save_mode & DO_DECODE) {
  1603. X    save_file = NULL;
  1604. X    c = uudecode(ah, art);
  1605. X    fclose(art);
  1606. X    return (c < 0) ? 0 : 1;
  1607. X    }
  1608. X
  1609. X    if (save_mode & UNIQUE_FILES) {
  1610. X    uniqbuf[0] = NUL;
  1611. X    do {
  1612. X        strcpy(copybuf, uniqbuf);
  1613. X        uniq_counter++;
  1614. X        sprintf(uniqbuf, uniq_format, uniq_counter + save_counter_offset);
  1615. X        if (strcmp(uniqbuf, copybuf) == 0) {
  1616. X        msg("save-counter \"%s\" does not generate unique file names",
  1617. X            save_counter_format);
  1618. X        goto fatal;
  1619. X        }
  1620. X    } while (file_exist(uniqbuf, (char *)NULL));
  1621. X    save_name = uniqbuf;
  1622. X    save_mode |= FILE_IS_NEW;
  1623. X    } else
  1624. X    if (save_mode & APPEND_ARTNUM) {
  1625. X        sprintf(uniqbuf, uniq_format, (long)(ah->a_number));
  1626. X        save_name = uniqbuf;
  1627. X    }
  1628. X
  1629. X    if (save_mode & FILE_IS_NEW) {
  1630. X    if (save_mode & (IS_PIPE | DO_UNSHAR)) {
  1631. X        set_visual = 1;
  1632. X        was_raw = visual_off();
  1633. X    }
  1634. X
  1635. X    if (save_mode & IS_PIPE) {
  1636. X        if ((save_file = popen(save_name, "w")) == NULL) {
  1637. X        msg("Cannot pipe to %s", save_name);
  1638. X        goto fatal;
  1639. X        }
  1640. X    } else
  1641. X    if (save_mode & DO_UNSHAR) {
  1642. X        if ((save_mode & DO_PATCH) == 0) {
  1643. X        if (!unshar_position(art))
  1644. X            goto fatal;
  1645. X        if (unshar_header_file)
  1646. X            store_header(ah, art, save_name, unshar_header_file);
  1647. X        }
  1648. X
  1649. X        new_temp_file();
  1650. X        sprintf(copybuf,
  1651. X            "cd %s && { %s 2>&1 ; } | tee %s ; cat %s >> %s.Result ; rm %s",
  1652. X            save_name != NULL ? save_name : ".", unshar_cmd,
  1653. X            temp_file, temp_file,
  1654. X            (save_mode & DO_PATCH) ? "Patch" : "Unshar",
  1655. X            temp_file);
  1656. X
  1657. X        save_file = popen(copybuf, "w");
  1658. X        if (save_file == NULL) {
  1659. X        msg("Cannot exec: '%s'", copybuf);
  1660. X        goto fatal;
  1661. X        }
  1662. X        printf("\r\n%s %s\r\n",
  1663. X           save_mode & DO_PATCH ? "PATCHING FROM" : "UNPACKING",
  1664. X           ah->subject ? ah->subject : "ARTICLE"); fl;
  1665. X    } else {
  1666. X        if ((save_file = open_file(save_name, OPEN_APPEND)) == NULL) {
  1667. X        msg("Cannot write %s", save_name);
  1668. X        fclose(art);
  1669. X        return 0;
  1670. X        }
  1671. X        if (ftell(save_file) != (off_t)0)
  1672. X        save_mode &= ~FILE_IS_NEW;
  1673. X    }
  1674. X    }
  1675. X
  1676. X    clrline();
  1677. X    s_pipe = 0;
  1678. X
  1679. X    if (mode != NO_HEADER)
  1680. X    mailbox_format(save_file, 1);
  1681. X
  1682. X    if (mode == FULL_HEADER) {
  1683. X    off_t cnt = ah->fpos - ah->hpos;
  1684. X    while (--cnt >= 0) {
  1685. X        if ((c = getc(art)) == EOF) break;
  1686. X        putc(c, save_file);
  1687. X    }
  1688. X    } else
  1689. X    if (mode == SHORT_HEADER) {
  1690. X    if (news.ng_from)
  1691. X        fprintf(save_file, "From: %s\n", news.ng_from);
  1692. X    if (news.ng_date)
  1693. X        fprintf(save_file, "Date: %s\n", news.ng_date);
  1694. X    if (news.ng_subj)
  1695. X        fprintf(save_file, "Subject: %s\n", news.ng_subj);
  1696. X    fputc(NL, save_file);
  1697. X    } else
  1698. X    if (mode == SHORT_HEADER_DG) {
  1699. X    if (digest.dg_from)
  1700. X        fprintf(save_file, "From: %s\n", digest.dg_from);
  1701. X    if (digest.dg_date)
  1702. X        fprintf(save_file, "Date: %s\n", digest.dg_date);
  1703. X    if (digest.dg_subj)
  1704. X        fprintf(save_file, "Subject: %s\n", digest.dg_subj);
  1705. X    fputc(NL, save_file);
  1706. X    }
  1707. X
  1708. X    fflush(save_file);
  1709. X    if (s_pipe) goto broken_pipe;
  1710. X
  1711. X    mode = mode != NO_HEADER && (save_mode & (IS_PIPE | DO_UNSHAR)) == 0;
  1712. X
  1713. X    lcount = 0;
  1714. X    while (ftell(art) < ah->lpos && fgets(copybuf, 512, art)) {
  1715. X    lcount++;
  1716. X    if (rot13_active) rot13_line(copybuf);
  1717. X    if (mode && is_header_line(copybuf))
  1718. X        fputc('~', save_file);
  1719. X    fputs(copybuf, save_file);
  1720. X    if (s_pipe) goto broken_pipe;
  1721. X    }
  1722. X
  1723. X    if (mode != NO_HEADER)
  1724. X    lcount += mailbox_format(save_file, 0);
  1725. X
  1726. Xbroken_pipe:
  1727. X    fclose(art);
  1728. X
  1729. X    if (save_mode & DO_UNSHAR) {
  1730. X    if ((c = pclose(save_file)) != 0)
  1731. X        sprintf(delayed_msg, "Save command failed; exit = %d", c);
  1732. X    save_file = NULL;
  1733. X    } else {
  1734. X    if (s_pipe)
  1735. X        msg("Command did not read complete article!");
  1736. X    else
  1737. X        if (save_report)
  1738. X        msg((save_mode & IS_PIPE)     ? "%s: %d lines piped" :
  1739. X        (save_mode & FILE_IS_NEW) ? "%s created: %d lines written" :
  1740. X        "%s: %d lines appended", save_name, lcount);
  1741. X
  1742. X    if (s_pipe || (save_mode & SEPARATE_FILES)) {
  1743. X        if (end_save() == 0) return 0;
  1744. X    } else
  1745. X        save_mode &= ~FILE_IS_NEW;
  1746. X    }
  1747. X
  1748. X#ifdef MAIL_READING
  1749. X    if (mail_auto_delete && (save_mode & IS_PIPE) == 0)
  1750. X    if (current_group->group_flag & G_MAILBOX)
  1751. X        if (ah->attr != A_CANCEL)
  1752. X        fcancel(ah);
  1753. X#endif
  1754. X    if (set_visual) visual_on();
  1755. X    if (was_raw) raw();
  1756. X
  1757. X    ah->flag |= A_ST_FILED;
  1758. X
  1759. X    return !s_pipe || (save_mode & SEPARATE_FILES);
  1760. X
  1761. X fatal:
  1762. X    if (set_visual) visual_on();
  1763. X    if (was_raw) raw();
  1764. X    fclose(art);
  1765. X    return 0;
  1766. X}
  1767. X
  1768. X
  1769. Xend_save()
  1770. X{
  1771. X    int c;
  1772. X    FILE *sf;
  1773. X    sf = save_file;
  1774. X    save_file = NULL;
  1775. X
  1776. X    if (sf) {
  1777. X    if (save_mode & (IS_PIPE | DO_UNSHAR)) {
  1778. X        if ((c = pclose(sf)) != 0) {
  1779. X        if (c >= 256) c >>= 8;    /* HACK */
  1780. X        sprintf(delayed_msg, "Save command failed; exit = %d", c);
  1781. X        return 0;
  1782. X        }
  1783. X    } else
  1784. X        fclose(sf);
  1785. X    }
  1786. X
  1787. X    if (save_mode & DO_DECODE) {
  1788. X    uud_end();
  1789. X    }
  1790. X    if (save_mode & DO_UNSHAR)
  1791. X    sprintf(delayed_msg, "Output is saved in %s/%s.Result",
  1792. X        save_name != NULL ? save_name : ".",
  1793. X        (save_mode & DO_PATCH) ? "Patch" : "Unshar");
  1794. X    return 1;
  1795. X}
  1796. X
  1797. Xstore_header(ah, f, dir, file)
  1798. Xarticle_header *ah;
  1799. XFILE *f;
  1800. Xchar *dir, *file;
  1801. X{
  1802. X    register int c;
  1803. X    off_t endpos;
  1804. X    FILE *h;
  1805. X
  1806. X    if (dir != (char *)NULL && file[0] != '/')
  1807. X    file = relative(dir, file);
  1808. X    if ((h = open_file(file, OPEN_APPEND)) == NULL) {
  1809. X    msg("Cannot open %s", file);
  1810. X    return;
  1811. X    }
  1812. X    fseek(h, (off_t)0, 2);
  1813. X    if (!use_mmdf_folders && ftell(h) > 0) putc(NL, h);  /* just in case */
  1814. X    mailbox_format(h, 1);
  1815. X    endpos = ftell(f) - ah->hpos;
  1816. X    fseek(f, ah->hpos, 0);
  1817. X    while (--endpos >= 0 && (c = getc(f)) != EOF)
  1818. X    putc(c, h);
  1819. X
  1820. X    mailbox_format(h, 0);
  1821. X    fclose(h);
  1822. X}
  1823. X
  1824. Xmailbox_format(f, top)
  1825. XFILE *f;
  1826. Xint top;
  1827. X{
  1828. X    time_t now;
  1829. X    char *ctime();
  1830. X
  1831. X    if (use_mmdf_folders) {
  1832. X    fprintf(f, "\001\001\001\001\n");
  1833. X    return 0;
  1834. X    }
  1835. X
  1836. X    if (top == 0) {
  1837. X    fputc(NL, f);
  1838. X    return 1;
  1839. X    }
  1840. X
  1841. X    if (use_mail_folders) {
  1842. X    now = cur_time();
  1843. X    fprintf(f, "From %s %s",
  1844. X        current_group->group_name, ctime(&now));
  1845. X    return 1;
  1846. X    }
  1847. X
  1848. X    return 0;
  1849. X}
  1850. X
  1851. Xchar *run_mkdir(dir, name_buf)
  1852. Xchar *dir, *name_buf;
  1853. X{
  1854. X    if (dir == NULL) {
  1855. X    prompt("\1Directory: \1");
  1856. X    dir = get_s(last_dir, NONE, NONE, file_completion);
  1857. X    if (dir == NULL || *dir == NUL) return NULL;
  1858. X    strcpy(last_dir, dir);
  1859. X    }
  1860. X
  1861. X    if (*dir == '+' || *dir == '~') {
  1862. X    if (!expand_file_name(name_buf, dir, 1))
  1863. X        return NULL;
  1864. X    dir = name_buf;
  1865. X    }
  1866. X
  1867. X    if (file_exist(dir, (char *)NULL)) {
  1868. X    msg("Directory %s already exists", dir);
  1869. X    return NULL;
  1870. X    }
  1871. X
  1872. X    if (mkdir(dir, 0755)) {
  1873. X    msg("Cannot make %s", dir);
  1874. X    return NULL;
  1875. X    }
  1876. X
  1877. X    return dir;
  1878. X}
  1879. X
  1880. X
  1881. Xchange_dir(dir, in_init)
  1882. Xchar *dir;
  1883. Xint in_init;
  1884. X{
  1885. X    char dir_buf[FILENAME];
  1886. X
  1887. X    if (dir == NULL) {
  1888. X    prompt("\1Directory: \1");
  1889. X    dir = get_s(last_dir, NONE, NONE, file_completion);
  1890. X    }
  1891. X
  1892. X    if (dir == NULL || *dir == NUL) return 0;
  1893. X
  1894. X    strcpy(last_dir, dir);
  1895. X
  1896. X    if (*dir == '+' || *dir == '~') {
  1897. X    if (!expand_file_name(dir_buf, dir, 1)) return in_init;
  1898. X    dir = dir_buf;
  1899. X    }
  1900. X
  1901. X    if (chdir(dir) == 0) {
  1902. X    if (!in_init) msg("Directory: %s", dir);
  1903. X    return 0;
  1904. X    }
  1905. X
  1906. X    if (in_init) return 1;
  1907. X
  1908. X    if (errno == EACCES)
  1909. X    msg("Cannot access directory %s", dir);
  1910. X    else {
  1911. X    /* should ask and create directory here */
  1912. X    msg("Directory not found: %s", dir);
  1913. X    }
  1914. X
  1915. X    return 0;
  1916. X}
  1917. X
  1918. X
  1919. END_OF_FILE
  1920.   if test 16732 -ne `wc -c <'save.c'`; then
  1921.     echo shar: \"'save.c'\" unpacked with wrong size!
  1922.   fi
  1923.   # end of 'save.c'
  1924. fi
  1925. echo shar: End of archive 15 \(of 22\).
  1926. cp /dev/null ark15isdone
  1927. MISSING=""
  1928. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ; do
  1929.     if test ! -f ark${I}isdone ; then
  1930.     MISSING="${MISSING} ${I}"
  1931.     fi
  1932. done
  1933. if test "${MISSING}" = "" ; then
  1934.     echo You have unpacked all 22 archives.
  1935.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1936. else
  1937.     echo You still must unpack the following archives:
  1938.     echo "        " ${MISSING}
  1939. fi
  1940. exit 0
  1941.